package lxs.swift.verify;


import lxs.swift.instance.StructInstance;
import lxs.swift.tool.Formatter;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;


public class Tester<T, E extends Throwable> {
    private final Function<String, E> errorFactory;
    private final List<Wrapper<T, ?>> wrappers = new ArrayList<>();

    public Tester(Function<String, E> errorFactory) {
        this.errorFactory = errorFactory;
    }

    public void expected(@NotNull Predicate<T> expect, @NotNull String errorFormat) {
        Wrapper<T, String> wrapper = new Wrapper<>(expect, errorFormat);
        wrappers.add(wrapper);
    }

    public void expected(@NotNull Predicate<T> expect, @NotNull Function<T, String> error) {
        Wrapper<T, Function<T, String>> wrapper = new Wrapper<>(expect, error);
        wrappers.add(wrapper);
    }

    public void unexpected(@NotNull Predicate<T> unExpect, @NotNull String errorFormat) {
        Wrapper<T, String> wrapper = new Wrapper<>(unExpect.negate(), errorFormat);
        wrappers.add(wrapper);
    }

    public void unexpected(@NotNull Predicate<T> unExpect, @NotNull Function<T, String> error) {
        Wrapper<T, Function<T, String>> wrapper = new Wrapper<>(unExpect.negate(), error);
        wrappers.add(wrapper);
    }

    public void execOnExpected(@NotNull Predicate<T> expect, @NotNull Consumer<T> consumer) {
        Wrapper<T, Consumer<T>> wrapper = new Wrapper<>(expect, consumer);
        wrappers.add(wrapper);
    }

    public void execOnNotExpected(@NotNull Predicate<T> unExpect, @NotNull Consumer<T> consumer) {
        Wrapper<T, Consumer<T>> wrapper = new Wrapper<>(unExpect.negate(), consumer);
        wrappers.add(wrapper);
    }

    public void test(T value) throws E {
        StructInstance<?> instance = StructInstance.from(value);
        for (Wrapper<T, ?> wrapper : wrappers) {
            boolean test = wrapper.predicate.test(value);
            if (test) continue;
            Object fn = wrapper.value;
            if (fn instanceof Consumer) {
                Consumer<T> accept = (Consumer<T>) fn;
                accept.accept(value);
                continue;
            }
            String error;
            if (fn instanceof Function) {
                Function<T, String> apply = (Function<T, String>) fn;
                error = apply.apply(value);
            } else {
                error = fn.toString();
                error = Formatter.fmtSmart(error, instance);
            }
            throw errorFactory.apply(error);
        }
    }

    private static class Wrapper<T, V> {
        public final Predicate<T> predicate;
        public final V value;

        public Wrapper(Predicate<T> predicate, V value) {
            this.predicate = predicate;
            this.value = value;
        }
    }

}
